package com.fhhst.prodroid;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Printcore Implementation for Android Uses a Printerclass, which is used as
 * Universal interface to a PRotos Printer
 * 
 * Uses UsbSerialLibrary, an Android USB host serial driver library for CDC,
 * FTDI, Arduino and other devices written by mike wakerly (opensource@hoho.com)
 * 
 * @author Robert Bremer 2013
 */
public class Printcore {

	private String[] mainqueue;
	private String[] priqueue;
	private String[] sentlines; // Lines already Sent to printer

	private float temperature;

	public float getTemperature() {
		return temperature;
	}

	public void setTemperature(float temperature) {
		this.temperature = temperature;
	}

	public float getBedtemperature() {
		return bedtemperature;
	}

	public void setBedtemperature(float bedtemperature) {
		this.bedtemperature = bedtemperature;
	}

	private float bedtemperature;

	private int lineno;
	private int queueindex;
	private int resendfrom;
	private boolean connected;
	private List<String> recvlines = new ArrayList<String>();
	private List<String> bufferedlines = new ArrayList<String>();
	private MainActivity lMainActivity;



	private Printer lPrinter;


	/**
	 * 
	 * @param aMainActivity
	 *            Parent activity with GUI
	 */
	public Printcore(MainActivity aMainActivity) {
		lMainActivity = aMainActivity;

		lineno = -1;
		queueindex = -1;
		resendfrom = -1;

		lPrinter = new Printer(this, lMainActivity);
		lPrinter.setOnline(false);
	}

	public void connect() {

		if (lPrinter.isConnected()) {
			disconnect();
		}

		send("M105", 0, false);
		enableListen();

	}

	public void disconnect() {

	}

	public boolean isOnline() {
		return lPrinter.isOnline();
	}
	public void pause() {
		lPrinter.pause();
	}

	public void resume() { // resumeprint?
		lPrinter.resume();
	}

	public void reset() {
		lPrinter.setOnline(false);
		lPrinter.reset();
	}

	public void readline() { // ????

	}

	/**
	 * Enable the reading and decoding of messages from the printer
	 */
	public void enableListen() {

		if (connected) {
			while (!lPrinter.isOnline()) {

				// send m105 every 5 ticks

			}
		}
		// go to the main activity and send a M105
	}

	/**
	 * @param acommand
	 *            Command as String
	 * @param lineno
	 *            last Linenumber
	 * @param calcchecksum
	 *            Calc Checksum of command and lineno
	 */
	private void send(String acommand, int lineno, boolean calcchecksum) {
		String lCommand;
		if (calcchecksum) {
			String sPrefix = "N" + lineno + " " + acommand;
			lCommand = sPrefix + "*" + checksum(sPrefix.toCharArray());
		} else
			lCommand = acommand;

		lCommand = lCommand + "\n";
		lPrinter.send(lCommand.getBytes());
	}

	/**
	 * @param data
	 *            array of received bytes
	 */
	/**
	 * @param data
	 */
	void updateReceivedData(byte[] data) {
		// final String message = "Read " + data.length + " bytes: \n"
		// + HexDump.dumpHexString(data) + "\n\n";
		String message = null;
		try {
			message = new String(data, "UTF8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} // for UTF8 encoding

		// Split lines by lineBreak
		// if no linebreak at the end of the message is found, put it on a stack
		// and read the next message
		// TODO: Timeout for the following message
		if (message.charAt(message.length() - 1) == '\n') {
			if (bufferedlines.isEmpty()) {
				String[] splitMessage = message.split("[\\r\\n]+");
				for (int i = 0; i < splitMessage.length; i++) {
					recvlines.add(splitMessage[i]);
				}
				readlines();
			} else {
				bufferedlines.add(message);

				StringBuilder builder = new StringBuilder();
				for (String value : bufferedlines) {
					builder.append(value);
				}
				String newmessage = builder.toString();

				String[] splitMessage = newmessage.split("[\\r\\n]+");
				for (int i = 0; i < splitMessage.length; i++) {
					recvlines.add(splitMessage[i]);
				}
				bufferedlines.clear();
				readlines();
			}
		} else { // message has no line break
			bufferedlines.add(message);
		}

	}

	private void readlines() {

		if (recvlines.isEmpty()) {
			throw new IllegalArgumentException("Input cannot be empty");
		}

		if (lPrinter.isConnected()) {
			if (!lPrinter.isOnline()) { // printer offline
				checkAndSetOnline();

			} else {

			}
		}
		recvlines.clear();
	}

	/**
	 * Check if printer is online and search for ok pattern in recv data
	 */
	private void checkAndSetOnline() {

		for (int i = 0; i < recvlines.size(); i++) {
			String tmp = recvlines.get(i) + "\n";
			lMainActivity.logReceivedData(tmp);
			parseTemperatures(tmp);
			if ((recvlines.get(i).startsWith("ok"))
					|| (recvlines.get(i).startsWith("start") || (recvlines
							.get(i).startsWith("Grbl")))) {
				lPrinter.setOnline(true);
				lMainActivity.updateTitleView("Printer is online: "
						+ recvlines.get(i));

				break;

			}
		}
	}

	private void parseTemperatures(String aLine) {
		if ((aLine.startsWith("ok"))
				&& (aLine.contains("T:") && (aLine.contains("B:")))) {
			String[] splitLine = aLine.split(" ");

			if (splitLine[1].startsWith("T:"))
				setTemperature(Float.parseFloat(splitLine[1].substring(2)));

			if (splitLine[2].startsWith("B:"))
				setBedtemperature(Float.parseFloat(splitLine[2].substring(2)));

			lMainActivity.logReceivedData("Hotend Temp:" + getTemperature()
					+ "\n");
			lMainActivity.logReceivedData("Bed Temp:" + getBedtemperature()
					+ "\n");
		}

	}

	/**
	 * @param cmd
	 *            a N + Line Number
	 * @return checksum of a command according to http://reprap.org/wiki/G-code
	 */
	public int checksum(char[] cmd) {
		int cs = 0;
		for (int i = 0; (cmd[i] != '*') && (Character.isDefined(cmd[i])); i++)
			cs = cs ^ cmd[i];
		cs &= 0xff;
		return cs;
	}

	private boolean printerReadyForCommand() {
		if (lPrinter.isOnline()) {
			return true;
		} else {
			lMainActivity.logReceivedData(lMainActivity
					.getString(R.string.cant_send_printer_not_online));
			return false;
		}
	}

	/**
	 * @param aTemp
	 *            Temperature of the Hotend, 0 for off
	 */
	public boolean setHeatTemp(int aTemp) { // do_settemp

		if ((aTemp >= 0) && printerReadyForCommand()) {
			lMainActivity.logReceivedData("Setting hotend temperature to "
					+ aTemp + " degrees Celsius.");

			send("M104 S" + aTemp, 0, false);
			return true;
		} else
			return false;

	}

	/**
	 * @param aTemp
	 *            Temperature of the Bed, 0 for off
	 */
	public boolean setBedTemp(int aTemp) { // do_bedtemp
		if ((aTemp >= 0) && printerReadyForCommand()) {
			lMainActivity.logReceivedData("Setting bed temperature to " + aTemp
					+ " degrees Celsius.");

			send("M140 S" + aTemp, 0, false);
			return true;
		} else
			return false;
	}


}
